module xenGameState;

import xenocide;
import xenWindow;
import xenObjects;
import tango.util.container.LinkedList;
import tango.math.Math;
import svgDecoder;
import xenForces;

import blaze.all;

public struct Sprite
{
	uint ID,textureID,width,height,w2,h2;
	float scale;
	
	SDL_Surface* textureImage;
	/**
	 * Load the texture used in this tutorial.
	 */
	SDL_Surface* load(uint iID,char * iPath,uint iWidth,uint iHeight)
	{
		scale=1f;
		ID=iID;
		width=iWidth;
		height=iHeight;
		w2=cast(uint)(width/2);
		h2=cast(uint)(height/2);
		// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
		if ((textureImage = IMG_Load(iPath)) !is null)
		{
			Stdout.formatln("tex loaded");
		
			SDL_Surface* tf2=SDL_DisplayFormatAlpha(textureImage);
			SDL_FreeSurface(textureImage);
			// Free the surface when exiting the scope
			scope(exit)
				SDL_FreeSurface(textureImage);
		
			textureImage=tf2;
			// Create The Texture
			glGenTextures(1, &textureID);
			// Create Nearest Filtered Texture
			glBindTexture(GL_TEXTURE_2D, textureID);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureImage.w, textureImage.h, 0,
				GL_BGRA, GL_UNSIGNED_BYTE, textureImage.pixels);
			gluBuild2DMipmaps (GL_TEXTURE_2D, GL_RGBA, textureImage.w, textureImage.h,
				GL_BGRA, GL_UNSIGNED_BYTE, textureImage.pixels);
		}
		initDL();
		return textureImage;
	}
	void initDL()
	{
		glNewList(ID*10,GL_COMPILE);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glColor4f(1,1,1,1);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		
		glBindTexture(GL_TEXTURE_2D, textureID);
		
		glBegin(GL_QUADS);
		// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f);
		glVertex3i(-w2,-h2, 0);
		// Top Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f);
		glVertex3i(-w2, h2,0);
		// Top Left Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f);
		glVertex3i(w2,h2, 0);
		// Bottom Left Of The Texture and Quad
		glTexCoord2f(0.0f, 0.0f);
		glVertex3i(w2, -h2, 0);
		glEnd();
		//glPopMatrix();
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_BLEND);
	
		glEndList();
		
		
	}
	void draw()
	{
		glPushMatrix();
		glScalef(scale,scale,1);
		glCallList(ID*10);
		glPopMatrix();
	}
	
	
}
class xenGS:GameState
{
	SubWindow[] windows;
	
	uint nextID;
	bzBoundaryListener m_boundaryListener;
	bzContactListener m_contactListener;
	bzWorld world;
	// Drawlists for standard GL
	enum DL
	{
		SelBox=1,
		radarFan
		
	}
	
	int[2] worldDimensions;
	LinkedList!(BaseActor) actors;
	LinkedList!(Beacon) beacons;
	LinkedList!(Selectable) selectedObjects;
	Iceberg[] icebergs;
	
	bzBody staticSceneBody;
	
	xenLevel level;
	
	
	Layout!(char) sprint;
	
	float[][][] silhouettes;
	static enum SilhouetteStyle {standard=0,base};
	static char[][] SilhouetteFilenames=["silhouette_actor","silhouette_base"];
	
	float[2] basePosition;
	
	float[360] icos,isin;

	this(Logger iLog,char[] iName,char[] iTitle,XenApp iWindow)
	{
        
		super(iLog,iName,iTitle,iWindow);
		sprint = new Layout!(char)();
		nextID=10;
		
		
        actors=new LinkedList!(BaseActor)();
		beacons=new LinkedList!(Beacon)();
		
		selectedObjects=new LinkedList!(Selectable)();
		
		
		initLevel();
		windows=[new SubWindow3D(this,[0,0],app.size,level.basePos,0.01f)];
		initTrigo();
		initDLFan(30);
		
		initPhysics();
        
		setScene();
		
		float[2] pos,pos2;
		
		pos[0]=basePosition[0]+50f;
		pos[1]=basePosition[1]+10f;
		pos2[0]=basePosition[0]-50f;
		pos2[1]=basePosition[1]-10f;
		
		spawnActor(pos);
		spawnBeacon(pos2);
		pos2[1]=basePosition[1]-50f;	
		spawnBeacon(pos2);
		
	}
	uint getNewID()
	{
		return (nextID++);
	}
	void setScene()
	{
		int i,n,j;
		bzBodyDef bd = new bzBodyDef;
		bd.position.set( 0.0f, 0.0f );
		
		staticSceneBody=world.createBody(bd);
		
		
		bzEdgeDef edgeDef = new bzEdgeDef;
		foreach( ic;icebergs)
		{
			n=edgeDef.vertices.length=ic.silhouette.nVert;
			//Stdout.formatln(" *** iceberg name {}",ic.name);
			j=0;
			for (i=0;i<n;i++)
			{
				
				edgeDef.vertices[i].set(ic.silhouette.silhouette[j++],ic.silhouette.silhouette[j++]);
				//Stdout.formatln(" iceberg pos {}/{}",edgeDef.vertices[i].x,edgeDef.vertices[i].x);
			}
			staticSceneBody.createShape(edgeDef);
		}
		
	}

	void initPhysics()
	{
		// Define the gravity vector.
		bzVec2 gravity = bzVec2(0.0f, 00.0f);
	
		// Do we want to let bodies sleep?
		bool doSleep = true;
	
		// Define world boundaries
		bzAABB worldAABB;
		worldAABB.lowerBound.set(-worldDimensions[0], -worldDimensions[1]);
		worldAABB.upperBound.set(worldDimensions[0]*2, worldDimensions[1]*2);
			
		// Construct a world object, which will hold and simulate the rigid bodies.
		world = new bzWorld(worldAABB, gravity, doSleep);
		world.warmStarting = true;
		world.continuousPhysics=true;
/*
		m_boundaryListener = new BoundaryListener(this);
		// bzContact callback
		m_contactListener = new ContactListener(this);
		
		world.boundaryListener = m_boundaryListener;
		world.contactListener = m_contactListener;
		*/

	}
	

	void initTrigo()
	{
		float degtorad=3.141592f/180f;
		
		for(int i=0;i<360;i++)
		{
			icos[i]=(cos(i*degtorad));
			isin[i]=(sin(i*degtorad));
		}
		
	}
	void initDLFan(int iwidth)
	{
		int iangle=-iwidth;
		int step=5;
		int n=1,ia;
		float intens;
		glNewList(DL.radarFan,GL_COMPILE);
		glBegin(GL_TRIANGLE_FAN);
			glColor4f (0.,1.0,0.0,0.2);
			glVertex2f(0f,0f);
		
			while (iangle<iwidth)
			{
				intens=(cast(float)(iwidth-abs(iangle)))/iwidth;
				iangle+=step;
				ia=iangle%360;
				while (ia<0) ia+=360;
				
				
				glColor4f (0.,intens,0.,intens*0.5);
				glVertex2f(icos[ia] ,isin[ia]);
			}
		glEnd();
		glEndList();
		
    
		
	}
	
	float polygonArea(float[][] polygon)
	{
	   int i,j,N;
	   float area = 0;
	
	   N=polygon.length;
	
	   for (i=0;i<N;i++) {
		  j = (i + 1) % N;
		  area += polygon[i][0] * polygon[j][1];
		  area -= polygon[i][1] * polygon[j][0];
	   }
	
	   area *= 0.5f;
	   return area;
	}
	void initLevel()
	{
		Iceberg icb;
		
		level.load("res/level1.svg");

		basePosition[]=level.basePos;
		
		worldDimensions[]=[level.width,level.height];
		
		int n=level.paths.length;
		icebergs=new Iceberg[n];
		bool invert;
		int i=0;
		foreach (ps;level.paths)
		{
			invert=(polygonArea(ps)>0);
			icebergs[i++]=new Iceberg(this,sprint("Iceberg #{}",nextID),nextID++,ps,invert,0.4f);
			//logger.info(sprint("Iceberg #{} created",iid-1));
		}
		
		xenSVGDecoder svg;
		silhouettes.length=SilhouetteFilenames.length;
		
		i=0;
		foreach (s;SilhouetteFilenames)
		{
			svg.load("res/"~s~".svg");
			silhouettes[i].length=svg.paths.length;
			silhouettes[i][]=svg.paths;
			i++;
			
		}
		
		
		
	}
	void draw(float dt)
	{
		
		foreach (w;windows)
		{
			w.draw(dt);
			
			
		}
        
        
    }

	void step(float dt)
	{
		float timeStep = dt;
		int velocityIterations = 4;
		int positionIterations = 2;
		
		world.step(dt, velocityIterations, positionIterations);

		
		foreach (a;actors)
		{
			a.step(dt);
			
			
		}
		
		
	}

	int on_mouse_move(int x,int y,int xrel,int yrel,int button,int shift,int ctrl,int alt)
	{
		int result=0;
		
		foreach (w;windows)
		{
			result=w.on_mouse_move( x, y,xrel,yrel, button, shift, ctrl, alt);
			if (result!=0) break;
		
		}
		return result;
	}
	int on_mouse_press(int x,int y,int button,int shift,int ctrl,int alt)
	{
		
		int result=0;
		
		foreach (w;windows)
		{
			result=w.on_mouse_press( x, y, button, shift, ctrl, alt);
			if (result!=0) break;
		
		}
		return result;
		
	}
	int on_key_press(int key,bool shift,bool ctrl,bool alt)
	{
		int result=0;
		
		if (key==SDLK_1)
		{
			selectActor(spawnActor(basePosition),true);
			return 1;
		}
		
		foreach (a;selectedObjects)
		{
			result=a.on_key_press(key,shift,ctrl,alt);
			
		
		}
		foreach (w;windows)
		{
			result=w.on_key_press(key,shift,ctrl,alt);
			if (result!=0) break;
		
		}
		return result;
	}
	int on_key_release(int key,bool shift,bool ctrl,bool alt)
	{
		int result=0;
		
		foreach (w;windows)
		{
			result=w.on_key_release(key,shift,ctrl,alt);
			if (result!=0) break;
		
		}
		return result;
	}
	
	int on_mouse_release(int x,int y,int button,int shift,int ctrl,int alt)
	{
		
		int result=0;
		
		foreach (w;windows)
		{
			result=w.on_mouse_release( x, y, button, shift, ctrl, alt);
			if (result!=0) break;
		
		}
		return result;
		
	}
	
	BaseActor spawnActor(float[2] iPos)
	{
		BaseActor a=new BaseActor(this,sprint("Actor #{}",nextID),nextID++,iPos,silhouettes[SilhouetteStyle.standard]);
			
		if (a)
		{
			actors.add(a);
			a.components[a.ComponentID.radar]=new Radar(a,[0f,0f],0f,40f);
			
		}
		
		
		return a;
	}
	Beacon spawnBeacon(float[2] iPos)
	{
		auto a=new Beacon(this,sprint("Beacon #{}",nextID),iPos);
		if (a)
		{
			beacons.add(a);
			
		}
		
		
		return a;
	}
	bool selectActor(Selectable so,bool clearList)
	{
        if (!so.isSelectable())
		{
            Stdout.formatln("Error, not selectable ! ");
            return false;
		}
        if (clearList) clearSelected();
        
        selectedObjects.add(so);
        so.setSelected(true);
		Stdout.formatln("Object {} selected ! ",(cast(BaseObject)so).name);
        return true    ;
	}
	void clearSelected()
	{
        
        foreach(a ;selectedObjects)
		    a.setSelected(false);
            
        selectedObjects.clear();
	}
	LinkedList!(Selectable) doSelectionRectangle(float[2][2] box)
	{

        auto list=new LinkedList!(Selectable)();
        
        float s=1f;
        float x1,y1,x2,y2,d,x,y;

        x1=box[0][0];
		y1=box[0][1];
        x2=box[1][0];
		y2=box[1][1];
        d=(x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
        if (d<0.01f)
		{
            x=x1;
            y=y1;
            x1-=s;
            y1-=s;
            x2=x+s;
            y2=y+s;
        }   
        foreach( o;actors){
            if (o.isSelectable){
				x=o.position[0];
				y=o.position[1];
                if ((x1<x)&&(x<x2)&& (y1<y)&&(y<y2))
                    list.add(o);			}
        }
		if (list.size==0)
		{
			foreach( o;beacons){
				if (o.isSelectable){
					x=o.position[0];
					y=o.position[1];
					if ((x1<x)&&(x<x2)&& (y1<y)&&(y<y2))
						list.add(o);				}
        
			}
		}
        return list;
	}
	Beacon getClosestBeacon(float[2]pos)
	{
		auto dist=float.max;
		float d;
		Beacon B=null;
		foreach (b;beacons)
		{
			d=b.distanceTo(pos);
			if (d<dist)
			{
				B=b;
				dist=d;
			}
		}
		return B;
	}
	LinkedList!(float[]) getDijkstraPath(float[2] start,float[2] finish)
	{
		Beacon A,B;
		A=getClosestBeacon(start);
		B=getClosestBeacon(finish);
		if ((A)&&(B))
		{
			auto DK=new Dijkstra.Dijkstra!(Beacon)(beacons);
			auto path=DK.solve(A,B);
			auto result=new LinkedList!(float[])();
			float[] p;
			p.length=2;
			p[]=finish;
					
			result.add(finish);
			if (path !is null)
			{
				
				while (!path.isEmpty())
				{
					
					auto b=path.removeTail();
					result.add(b.position);
				}
			}
			
			return result;
 		}
		return null;
	}
}
	
class GameState
{
    char[] name;
    XenApp app;
	char[] title;
	Logger logger;
	Layout!(char) sprint;

	static enum KeyCode {A=97,D=100,F=102,S=115,M=109,K1=49,K2=50,K3=51,K4=52,K5=53};
	
    this(Logger iLog,char[] iName,char[] iTitle,XenApp iWindow)
	{
        name=iName;
        title=iTitle;
        app=iWindow;
		logger=iLog;
		sprint=app.L;
        
        
    }
	void draw	(float dt)
	{
	}
	
	int on_mouse_move(int x,int y,int xrel,int yrel,int button,int shift,int ctrl,int alt)
	{
		return 0;
	}
	int on_mouse_press(int x,int y,int button,int shift,int ctrl,int alt)
	{
		return 0;
	}
	int on_mouse_release(int x,int y,int button,int shift,int ctrl,int alt)
	{
		return 0;
	}
	int on_key_press(int key,bool shift,bool ctrl,bool alt)
	{
		return 0;	
	}
	int on_key_release(int key,bool shift,bool ctrl,bool alt)
	{
		return 0;
	}
	
	void step(float dt)
	{}	
    /*    
    def activate(self):
        window.push_handlers(self)
        
    def deActivate(self):
        window.remove_handlers(self)
        
    def step(dt):
        pass
    
    def draw(dt):
        pass
        
    def on_mouse_press( x, y, button, modifiers):
        return False
    
    def on_mouse_release( x, y, button, modifiers):
        return False
    
    def on_mouse_drag( x, y, dx, dy, buttons, modifiers):
        return False
    
    def on_key_press( symbol, modifiers):
        return False
	*/
}
